package $package;

import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

import javax.sql.DataSource;

import jmine.tec.persist.schema.creation.FullSchemaCreator;
import jmine.tec.persist.schema.impl.dialects.H2Dialect;
import jmine.tec.services.api.ServiceExecutionException;
import jmine.tec.services.api.ServicesPage;
import jmine.tec.services.api.execution.ServicesPageExecutor;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.InitializingBean;

import bancosys.tec.persist.authorization.AuthorizationManager;
import bancosys.tec.persist.authorization.AuthorizationStatus;
import bancosys.tec.services.input.driver.DigesterDriverServicesReader;
import bancosys.tec.utils.db.descriptor.FlatReferenceDatabaseBuilder;
import bancosys.tec.utils.db.descriptor.FlatReferenceDatabaseBuilder.ReferenceDatabaseDescriptionType;
import bancosys.tec.utils.db.descriptor.ReferenceDatabaseDescription;
import bancosys.tec.utils.db.descriptor.ReferenceDatabaseEntry;
import bancosys.tec.utils.db.descriptor.ReferenceDatabaseParser;
import bancosys.tec.utils.db.executor.CouldNotExecuteException;
import bancosys.tec.utils.db.executor.JdbcDbExecutor;

/**
 * Classe responsavel por carregar a base de referencia durante a inicializacao do aplicativo quando em teste
 */
public class ReferenceDatabaseLoader implements InitializingBean {

    private static final Log LOGGER = LogFactory.getLog(ReferenceDatabaseLoader.class);

    private JdbcDbExecutor executor;

    private DataSource dataSource;

    private SessionFactory sessionFactory;

    private List<String> dbResources;

    private DigesterDriverServicesReader servicesReader;

    private ServicesPageExecutor servicesPageExecutor;

    /**
     * {@inheritDoc}
     */
    public void afterPropertiesSet() throws Exception {
        Connection connection = this.getDataSource().getConnection();
        connection.setAutoCommit(true);
        try {
            FullSchemaCreator creator = new FullSchemaCreator(new H2Dialect());
            this.getExecutor().configure(connection, null);
            LOGGER.info("Creating schema");
            String[] statements = creator.createStatements(getSessionFactory());
            LOGGER.info("Executing schema statements");
            for (String statement : statements) {
                this.getExecutor().executeSingleStatement(statement);
            }
        } finally {
            connection.close();
        }
        LOGGER.info("Loading reference database files");
        AuthorizationManager.setCurrentThreadAuthorizationStatus(AuthorizationStatus.DISABLED);
        try {
            ReferenceDatabaseParser parser = new ReferenceDatabaseParser();
            List<ReferenceDatabaseDescription> parsed = parser.getDescriptions(dbResources.toArray(new String[this.dbResources.size()]));
            List<ReferenceDatabaseEntry> flat = FlatReferenceDatabaseBuilder.flatten(parsed, ReferenceDatabaseDescriptionType.POSTSCRIPT);
            for (ReferenceDatabaseEntry referenceDatabaseEntry : flat) {
                this.execute(referenceDatabaseEntry);
            }
            flat = FlatReferenceDatabaseBuilder.flatten(parsed, ReferenceDatabaseDescriptionType.REFERENCE);
            for (ReferenceDatabaseEntry referenceDatabaseEntry : flat) {
                this.execute(referenceDatabaseEntry);
            }
        } finally {
            AuthorizationManager.setCurrentThreadAuthorizationStatus(AuthorizationStatus.ENABLED);
        }
    }

    /**
     * Executa uma entrada da base de referencia. Ela pode ser do tipo sql ou service
     * 
     * @param entry {@link ReferenceDatabaseEntry}
     * @throws SQLException e
     * @throws CouldNotExecuteException e
     * @throws IOException e
     * @throws ServiceExecutionException e
     */
    private void execute(ReferenceDatabaseEntry entry) throws SQLException, CouldNotExecuteException, IOException,
            ServiceExecutionException {
        LOGGER.info("Loading file: " + entry.getClasspathResourcePath());
        if ("sql".equalsIgnoreCase(entry.getType())) {
            this.executeSQLFile(entry.getClasspathResourcePath());
        } else if ("service".equalsIgnoreCase(entry.getType())) {
            this.executeService(entry.getClasspathResourcePath());
        }
    }

    /**
     * Executa um arquivo de servicos - pode ser um XML ou XLS
     * 
     * @param classpathResourcePath o caminho (no classpath) para o arquivo
     * @throws IOException e
     * @throws ServiceExecutionException e
     */
    private void executeService(String classpathResourcePath) throws IOException, ServiceExecutionException {
        List<ServicesPage> services =
                this.getServicesReader().readServices(classpathResourcePath,
                        getClass().getClassLoader().getResourceAsStream(classpathResourcePath));
        this.getServicesPageExecutor().execute(services);
    }

    /**
     * Executa um SQL
     * 
     * @param classpathResourcePath o caminho para o arquivo
     * @throws SQLException e
     * @throws CouldNotExecuteException e
     */
    private void executeSQLFile(String classpathResourcePath) throws SQLException, CouldNotExecuteException {
        Connection conn = this.getDataSource().getConnection();
        conn.setAutoCommit(true);
        try {
            this.getExecutor().configure(conn, this.getClass().getClassLoader().getResource(classpathResourcePath));
            this.getExecutor().execute();
        } finally {
            conn.close();
        }
    }

    /**
     * @return the executor
     */
    public JdbcDbExecutor getExecutor() {
        return executor;
    }

    /**
     * @param executor the executor to set
     */
    public void setExecutor(JdbcDbExecutor executor) {
        this.executor = executor;
    }

    /**
     * @return the dataSource
     */
    public DataSource getDataSource() {
        return dataSource;
    }

    /**
     * @param dataSource the dataSource to set
     */
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    /**
     * @return the sessionFactory
     */
    public SessionFactory getSessionFactory() {
        return sessionFactory;
    }

    /**
     * @param sessionFactory the sessionFactory to set
     */
    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    /**
     * @return the dbResources
     */
    public List<String> getDbResources() {
        return dbResources;
    }

    /**
     * @param dbResources the dbResources to set
     */
    public void setDbResources(List<String> dbResources) {
        this.dbResources = dbResources;
    }

    /**
     * @return the servicesReader
     */
    public DigesterDriverServicesReader getServicesReader() {
        return servicesReader;
    }

    /**
     * @param servicesReader the servicesReader to set
     */
    public void setServicesReader(DigesterDriverServicesReader servicesReader) {
        this.servicesReader = servicesReader;
    }

    /**
     * @return the servicesPageExecutor
     */
    public ServicesPageExecutor getServicesPageExecutor() {
        return servicesPageExecutor;
    }

    /**
     * @param servicesPageExecutor the servicesPageExecutor to set
     */
    public void setServicesPageExecutor(ServicesPageExecutor servicesPageExecutor) {
        this.servicesPageExecutor = servicesPageExecutor;
    }

}
